package net.fornwall.eclipsecoder.perlsupport;

import java.lang.reflect.Array;
import java.util.regex.Pattern;

import net.fornwall.eclipsecoder.stats.CodeGenerator;
import net.fornwall.eclipsecoder.stats.ProblemStatement;

public class PerlCodeGenerator extends CodeGenerator {

    private static final String PERL_SIGNATURE = "$PERL_SIGNATURE$";

    public PerlCodeGenerator(ProblemStatement problemStatement) {
        super(problemStatement);
    }

    private String getCreateStatement(Object value, String variableName) {
        return "my " + getTypeChar(value.getClass()) + variableName + " = " + getValueString(value)
                + ";\n";
    }

    @Override
    public String getDummyReturnString() {
        return "0";
    }

    @Override
    protected String getMethodParamsString() {
        StringBuilder builder = new StringBuilder();
        int i = 0;
        for (String name : problemStatement.getParameterNames()) {
            if (builder.length() != 0)
                builder.append(", ");
            builder.append("\\$" + name);
            i++;
        }
        return builder.toString();
    }

    private String getSignature() {
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < problemStatement.getParameterTypes().size(); i++) {
            if (builder.length() != 0)
                builder.append(", ");
            builder.append(problemStatement.getParameterTypes().get(i).isArray() ? "\\\\@" : "\\$");
            i++;
        }
        return builder.toString();
    }

    @Override
    public String getSolutionStub(String codeTemplate) {
        return super.getSolutionStub(codeTemplate).replaceAll(Pattern.quote(PERL_SIGNATURE),
                getSignature());
    }

    @Override
    public String getTestsSource() {
        StringBuilder builder = new StringBuilder("package "
                + problemStatement.getSolutionClassName()
                + "Test;\nuse base qw(Test::Unit::TestCase);\nuse "
                + problemStatement.getSolutionClassName() + ";\nuse strict;\n"
                + "my $solution = new " + problemStatement.getSolutionClassName() + ";\n\n");
        int count = 0;

        for (ProblemStatement.TestCase testCase : problemStatement.getTestCases()) {
            builder.append("sub test_" + count + " {\n\tmy $self = shift;\n");

            for (int i = 0; i < testCase.getParameters().length; i++) {
                builder.append("\t");
                builder.append(getCreateStatement(testCase.getParameters()[i], problemStatement
                        .getParameterNames().get(i)));
            }

            builder.append("\n\t");
            builder.append(getCreateStatement(testCase.returnValue, "expected"));
            builder.append("\t");
            char returnTypeChar = problemStatement.getReturnType().isArray() ? '@' : '$';
            builder.append("my " + returnTypeChar + "actual = $solution->"
                    + problemStatement.getSolutionMethodName() + "(");
            for (int i = 0; i < testCase.getParameters().length; i++) {
                if (i != 0)
                    builder.append(", ");
                builder.append(testCase.getParameters()[i].getClass().isArray() ? "@" : "$");
                builder.append(problemStatement.getParameterNames().get(i));
            }
            builder.append(");\n");

            // TODO: Use special equal for double..
            builder.append("\t$self->assert_equals(" + returnTypeChar + "expected, "
                    + returnTypeChar + "actual);\n");
            builder.append("}\n\n");
            count++;
        }

        builder
                .append("use Test::Unit::HarnessUnit;\nmy $r = Test::Unit::HarnessUnit->new();\n$r->start('"
                        + problemStatement.getSolutionClassName() + "Test');\n");

        return builder.toString();
    }

    private char getTypeChar(Class<?> c) {
        return c.isArray() ? '@' : '$';
    }

    @Override
    public String getTypeString(Class<?> type) {
        return "";
    }

    private String getValueString(Object value) {
        Class<?> type = value.getClass();
        if (type == Integer.class || type == Long.class || type == Double.class) {
            return value.toString();
        } else if (type == String.class) {
            return "'" + value.toString() + "'";
        } else if (type.isArray()) {
            StringBuilder builder = new StringBuilder("[");
            for (int i = 0; i < Array.getLength(value); i++) {
                if (i != 0)
                    builder.append(", ");
                builder.append(getValueString(Array.get(value, i)));
            }
            builder.append("]");
            return builder.toString();
        } else {
            throw new IllegalArgumentException("Got type \"" + type + "\"");
        }
    }

}
